﻿
using System;
using System.Collections.Generic;
using System.Linq;

#if WinRT
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;
#else
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Media;
#endif


namespace DailyFx.UI
{

    /// <summary>
    ///		Helper methods for UI-related tasks.
    /// 
    ///		This class was obtained from Philip Sumi (a fellow WPF Discples blog)
    ///		http://www.hardcodet.net/uploads/2009/06/UIHelper.cs
    /// </summary>
    public static class TreeHelper
    {

        /// <summary>
		///     Finds a parent that matches the submitted type parameter of a given item on the visual tree .
        /// </summary>
        public static T TryFindParent<T> ( this DependencyObject child) where T : DependencyObject
        {
            //get parent item
            DependencyObject parentObject = GetParentObject(child);

            //we've reached the end of the tree
            if (parentObject == null) return null;

            //check if the parent matches the type we're looking for
            T parent = parentObject as T;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                //use recursion to proceed with next level
                return TryFindParent<T>(parentObject);
            }
        }

        /// <summary>
        ///		This method is an alternative to WPF's <see cref="VisualTreeHelper.GetParent"/> method, which also supports content elements. 
		///		Keep in mind that for content element, this method falls back to the logical tree of the element!
        /// </summary>
        public static DependencyObject GetParentObject ( this DependencyObject child )
        {
            if (child == null) return null;

            //handle content elements separately
            ContentElement contentElement = child as ContentElement;
            if (contentElement != null)
            {
                DependencyObject parent = ContentOperations.GetParent(contentElement);
                if (parent != null) return parent;

                FrameworkContentElement fce = contentElement as FrameworkContentElement;
                return fce != null ? fce.Parent : null;
            }

            //also try searching for parent in framework elements (such as DockPanel, etc)
            FrameworkElement frameworkElement = child as FrameworkElement;
            if (frameworkElement != null)
            {
                DependencyObject parent = frameworkElement.Parent;
                if (parent != null) return parent;
            }

            //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
            return VisualTreeHelper.GetParent(child) ;
        }


        /// <summary>
        ///		Analyzes both visual and logical tree in order to find all elements of a given type 
        ///		that are descendants of the <paramref name="source"/> item.
        /// </summary>
        public static IEnumerable<T> FindChildren<T>( this DependencyObject source) where T : DependencyObject
        {
            if ( source != null )
            {
                var childs = GetChildObjects( source );
                foreach (DependencyObject child in childs)
                {
                    //analyze if children match the requested type
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }

                    //recurse tree
                    foreach (T descendant in FindChildren<T>(child))
                    {
                        yield return descendant;
                    }
                }
            }
        }


        /// <summary>
        ///		This method is an alternative to WPF's <see cref="VisualTreeHelper.GetChild"/> method, which also supports content elements. 
        ///		Keep in mind that for content elements, this method falls back to the logical tree of the element.
        /// </summary>
        public static IEnumerable<DependencyObject> GetChildObjects ( this DependencyObject parent )
        {
            if (parent == null) yield break;

            if (parent is ContentElement || parent is FrameworkElement)
            {
                //use the logical tree for content / framework elements
                foreach ( object c in LogicalTreeHelper.GetChildren(parent) )
                {
                    var dependencyObject = c as DependencyObject;
                    if (dependencyObject != null) yield return (DependencyObject)c;
                }
            }
            else
            {
                //use the visual tree per default
                int count = VisualTreeHelper.GetChildrenCount( parent );
                for (int i = 0; i < count; i++)
                {
                    yield return VisualTreeHelper.GetChild (parent, i);
                }
            }
        }



        /// <summary>
        ///		Tries to locate a given item within the visual tree, starting with the dependency object at a given position. 
        /// </summary>
        public static T TryFindFromPoint<T> ( UIElement reference, Point point) where T : DependencyObject
        {
            DependencyObject element = reference.InputHitTest(point) as DependencyObject;

            if (element == null)
                return null;
            else if (element is T)
                return (T)element;
            else
                return TryFindParent<T>(element);
        }


		/// <summary>
		///		Searches through the list of named elements looking for a case-insensitive match.
		/// </summary>
		public static FrameworkElement FindNamedElement (this IEnumerable<FrameworkElement> elements, string name )
		{
#if WinRT
            return elements.FirstOrDefault( x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase) );
#else
			return elements.FirstOrDefault( x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) );
#endif
		}

		/// <summary>
		///		Gets all the <see cref="FrameworkElement"/> instances with names in the scope.
		/// </summary>
		public static Func<DependencyObject, IEnumerable<FrameworkElement>> GetNamedChildren = element =>
		{
			var router = GetRouter(element);

			if ( router == null )
				throw new ArgumentNullException("router");

			if ( router.Root == null )
				throw new ArgumentException (
					String.Format("Root is null on the given {0}", typeof(TreeRouter)) );


			var children = new List<FrameworkElement>();
			
			var queue = new Queue<DependencyObject>(); queue.Enqueue( router.Root );
			while (queue.Count > 0)
			{
				var current = queue.Dequeue();
				var currentElement = current as FrameworkElement;

				if ( currentElement != null && !string.IsNullOrEmpty( currentElement.Name) )
					children.Add( currentElement );

				if (current is UserControl && !ReferenceEquals( current, router.Root) )
					continue;

				DependencyObject hopTarget;
				if ( router.TryGetHop(current, out hopTarget) )
				{
					queue.Enqueue( hopTarget );
					continue;
				}

#if NET
				var childCount = ( current is UIElement || current is UIElement3D || current is ContainerVisual
										? VisualTreeHelper.GetChildrenCount( current )
										: 0
								);
#else
                var childCount = VisualTreeHelper.GetChildrenCount( current );
#endif
				if ( childCount > 0 )
				{
					for ( var i = 0; i < childCount; i++ )
					{
						var childDo = VisualTreeHelper.GetChild( current, i );
						queue.Enqueue(childDo);
					}

#if WinRT
                    var page = current as Page;

                    if ( page != null ) 
					{
                        if ( page.BottomAppBar != null )
                            queue.Enqueue( page.BottomAppBar );

                        if ( page.TopAppBar != null )
                            queue.Enqueue( page.TopAppBar );
                    }
#endif
				}
				else
				{
					var contentControl = current as ContentControl;
					if ( contentControl != null )
					{
						if ( contentControl.Content is DependencyObject )
							queue.Enqueue( contentControl.Content as DependencyObject );
#if !SILVERLIGHT && !WinRT
						var headeredControl = contentControl as HeaderedContentControl;
						if ( headeredControl != null && headeredControl.Header is DependencyObject )
							queue.Enqueue( headeredControl.Header as DependencyObject );
#endif
					}

					var itemsControl = current as ItemsControl;
					if ( itemsControl != null )
					{
						itemsControl.Items.OfType<DependencyObject>().Apply(queue.Enqueue);
#if !SILVERLIGHT && !WinRT
						var headeredControl = itemsControl as HeaderedItemsControl;
						if ( headeredControl != null && headeredControl.Header is DependencyObject )
							queue.Enqueue( headeredControl.Header as DependencyObject );
#endif
					}
#if WinRT
                    var semanticZoom = current as SemanticZoom;
                    if ( semanticZoom != null )
					{
                        if (semanticZoom.ZoomedInView is DependencyObject)
                            queue.Enqueue(semanticZoom.ZoomedInView as DependencyObject);

                        if (semanticZoom.ZoomedOutView is DependencyObject)
                            queue.Enqueue(semanticZoom.ZoomedOutView as DependencyObject);
                    }
#endif
#if WinRT81
                    var hub = current as Hub;
                    if ( hub != null )
					{
                        if (hub.Header is DependencyObject)
                            queue.Enqueue(hub.Header as DependencyObject);

                        foreach(var section in hub.Sections)
						{
                            queue.Enqueue(section);
                        }
                    }

                    var hubSection = current as HubSection;
                    if (hubSection != null) 
					{
                        if (hubSection.Header is DependencyObject)
                            queue.Enqueue(hubSection.Header as DependencyObject);
                    }

                    var commandBar = current as CommandBar;
                    if (commandBar != null)
					{
                        foreach (var command in commandBar.PrimaryCommands)
						{
                            if (command is DependencyObject)
                                queue.Enqueue(command as DependencyObject);
                        }

                        foreach (var command in commandBar.SecondaryCommands)
						{
                            if (command is DependencyObject)
                                queue.Enqueue(command as DependencyObject);
                        }
                    }
#endif
					else
					{
						var currentType = current.GetType();
						// todo: try to stores the unknown type of element .
					}
				}
			}

			return children ;
		};


		public static Func<DependencyObject, TreeRouter> GetRouter = element =>
		{
			// you can provide your own router .
			return new TreeRouter( element ); 
		};

    }

	/// <summary>
	///		Maintains a connection in the visual tree of dependency objects 
	///		in order to record a route through it .
	/// </summary>
	public class TreeRouter
	{
		/// <summary>
		///		the root element that searching starts .
		/// </summary>
		DependencyObject root = null ;

		/// <summary>
		///		the stored path of searching .
		/// </summary>
		readonly Dictionary<DependencyObject, DependencyObject> path
			= new Dictionary<DependencyObject, DependencyObject>();


		/// <summary>
		///		c'tor 
		/// </summary>
		public TreeRouter ( DependencyObject element )
		{
			this.FindPath( element );
		}

		/// <summary>
		///		Gets or sets the starting point of the route.
		/// </summary>
		public DependencyObject Root
		{
			get
			{
				return this.root;
			}
		}

		/// <summary>
		///		Finds a path of dependency objects which traces through visual anscestry until:
		///		
		///		1. a root which is <see langword="null"/>,
		///		2. a <see cref="UserControl"/>, 
		///		3. a <c>Page</c> with a dependency object <c>Page.ContentProperty</c> value, 
		///		4. a dependency object with <see cref="View.IsScopeRootProperty"/> set to <see langword="true"/>. 
		///		
		///		NB: <see cref="ContentPresenter"/> and <see cref="ItemsPresenter"/> are included in the resulting <see cref="Stack"/>
		///		in order to track which item in an items control we are scoped to.
		/// </summary>
		public virtual void FindPath ( DependencyObject element )
		{
			DependencyObject contentPresenter = null;

			var current = element;
			var previous = element;

			while ( true )
			{
				if ( current == null )
				{
					current = previous;
					break;
				}

				if ( current is UserControl )
					break;
#if !SILVERLIGHT
				if ( current is Page )
				{
					current = ((Page)current).Content as DependencyObject ?? current;
					break;
				}
#endif
				if ( (bool)current.GetValue(View.IsScopeRootProperty) )
					break;

#if WinRT
                if ( root is AppBar )
				{
                    var frame = Window.Current.Content as Frame;
                    var page = (frame != null) ? frame.Content as Page : null;
                    if (page != null && (root == page.TopAppBar || root == page.BottomAppBar))
					{
                        root = page;
                        break;
                    }
                }
#endif

				if ( current is ContentPresenter )
				{
					contentPresenter = current;
				}
				else if (current is ItemsPresenter && contentPresenter != null)
				{
					this.AddHop( current, contentPresenter );
					contentPresenter = null;
				}

				previous = current;
				current = VisualTreeHelper.GetParent( previous );
			}

			this.root = current;
		}

		/// <summary>
		///		Adds a segment to the route .
		/// </summary>
		public void AddHop ( DependencyObject from, DependencyObject to )
		{
			if (from == null)
				throw new ArgumentNullException("from");

			if (to == null)
				throw new ArgumentNullException("to");


			if ( path.Count > 0 && !path.ContainsKey(from) && !path.ContainsKey(to)
				&& !path.ContainsValue(from) && !path.ContainsValue(from))
			{
				throw new ArgumentException("Hop pair not part of existing route.");
			}

			if (path.ContainsKey(to))
			{
				throw new ArgumentException("Cycle detected when adding hop.");
			}

			this.path[from] = to;
		}

		/// <summary>
		///		Tries to get a target dependency object given a source.
		/// </summary>
		public bool TryGetHop ( DependencyObject source, out DependencyObject target )
		{
			return path.TryGetValue(source, out target);
		}
	}
}